﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Owin.Hosting;
using nwrole.Module.lib;

namespace nwrole.SuperModule.service
{
    public static class SuperModule
    {
        private static List<ModuleInfo> _moduleInfos = new List<ModuleInfo>();
        private static ModuleSettings _settings;
        private static int _superModulePort;

        private static IDisposable _webApplication;

        private static ModuleDebug _debug;

        public static string ModulePath
        {
            get { return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Modules/"); }
        }

        static SuperModule()
        {
            _debug = new ModuleDebug(0, "SuperModule");
            _debug.RegisterLogger(new ModuleFileLogger(LogLevel.Debug));

            if (!Directory.Exists(ModulePath))
                Directory.CreateDirectory(ModulePath);

            _settings = new ModuleSettings("SuperModuleSettings.xml");

            LoadSettings();

            CheckForModules();
        }

        public static void Start()
        {
            try
            {
                StartAllModules();

                _webApplication = WebApp.Start<WebPipeline>("http://localhost:" + _superModulePort);
            }
            catch (Exception e)
            {
                Trace.TraceError("Exception during Start: " + e);
                throw;
            }
           
        }

        public static void Stop()
        {
            try
            {
                StopAllModules();

                _webApplication.Dispose();
            }
            catch (Exception e)
            {
                Trace.TraceError("Exception during Stop: " + e);
                throw;
            }
            
        }

        private static void LoadSettings()
        {
            _superModulePort = _settings.GetSetting("SuperModulePort", 5000);
            int numModules = _settings.GetSetting("NumModules", 0);
            
            _moduleInfos = new List<ModuleInfo>();

            Trace.TraceInformation("Loading settings");

            for(int i = 0; i < numModules; i++)
            {
                var module = new ModuleInfo();
                module.Name = _settings.GetSetting("Modules/Module" + i + "/Name", null);
                module.Filename = _settings.GetSetting("Modules/Module" + i + "/Filename", null);
                module.FullPathAndName = _settings.GetSetting("Modules/Module" + i + "/FullPathAndName", null);
                module.ServiceName = _settings.GetSetting("Modules/Module" + i + "/ServiceName", null);
                module.Filename = _settings.GetSetting("Modules/Module" + i + "/Username", null);
                module.Filename = _settings.GetSetting("Modules/Module" + i + "/Password", null);
                module.Enabled = bool.Parse(_settings.GetSetting("Modules/Module" + i + "/Enabled", false.ToString()));

                Trace.TraceInformation("Module in settings: " + module.Name);

                _moduleInfos.Add(module);
            }
        }

        private static void SaveSettings()
        {
            Trace.TraceInformation("Saving settings");

            _settings.PutSetting("SuperModulePort", _superModulePort);
            _settings.PutSetting("NumModules", _moduleInfos.Count);

            for(int i = 0; i < _moduleInfos.Count; i++)
            {
                var module = _moduleInfos[i];

                Trace.TraceInformation("Saving module info: " + module.Name);
                _settings.PutSetting("Modules/Module" + i + "/Name", module.Name);
                _settings.PutSetting("Modules/Module" + i + "/Filename", module.Filename);
                _settings.PutSetting("Modules/Module" + i + "/FullPathAndName", module.FullPathAndName);
                _settings.PutSetting("Modules/Module" + i + "/ServiceName", module.ServiceName);
                _settings.PutSetting("Modules/Module" + i + "/Username", module.Username);
                _settings.PutSetting("Modules/Module" + i + "/Password", module.Password);
                _settings.PutSetting("Modules/Module" + i + "/Enabled", module.Enabled.ToString());

                i++;
            }

            _settings.Save();
        }

        public static void CheckForModules()
        {
            Trace.TraceInformation("Checking for modules");

            bool changed = false;

            // Remove modules that no longer exist in Windows services
            foreach(var module in _moduleInfos.ToList())
            {
                Trace.TraceInformation("ModuleInfo in list: " + module.Name);
                if (!File.Exists(module.FullPathAndName))
                {
                    Trace.TraceInformation("Module does not exist");
                    _moduleInfos.Remove(module);
                    changed = true;
                }
            }
            
            foreach(var moduleFolder in Directory.EnumerateDirectories(ModulePath))
            {
                string moduleName = Path.GetFileName(moduleFolder);

                Trace.TraceInformation("Found module: " + moduleName);

                var exeFiles = Directory.EnumerateFiles(moduleFolder, "*.exe", SearchOption.TopDirectoryOnly);

                try
                {
                    foreach (var exeFile in exeFiles)
                    {
                        if (exeFile.EndsWith(".vshost.exe"))
                            continue;
                        
                        string filename = Path.GetFileName(exeFile);

                        Trace.TraceInformation("module exe: " + exeFile);

                        string serviceName = moduleName + "_" + Path.GetFileNameWithoutExtension(filename);
                        //string combinedName = moduleName + " - " + filename;
                        var moduleInfo = GetModuleFromServiceName(serviceName);
                        if (moduleInfo == null)
                        {
                            moduleInfo = new ModuleInfo();
                            moduleInfo.Name = moduleName;
                            moduleInfo.Filename = filename;
                            moduleInfo.FullPathAndName = exeFile;
                            //moduleInfo.CombinedName = combinedName;
                            moduleInfo.ServiceName = serviceName;

                            _moduleInfos.Add(moduleInfo);

                            SetupModule(moduleName);

                            changed = true;
                        }

                        moduleInfo.Status = GetServiceStatus(moduleInfo.ServiceName);

                        break;
                    }
                }
                catch(Exception e)
                {
                    Trace.TraceError("Error adding module " + moduleName + ": " + e);
                }
                
            }

            if(changed)
                SaveSettings();
        }

        public static void StartAllModules()
        {
            foreach (var module in _moduleInfos.ToList())
            {
                if(module.Enabled)
                    StartModule(module.Name);
            }
        }

        public static void StopAllModules()
        {
            foreach (var module in _moduleInfos.ToList())
            {
                try
                {
                    StopModule(module.Name);
                }
                catch (Exception e)
                {
                    Trace.TraceError("Unable to stop module: " + e);
                }
                
            }
        }

        public static void SetupModule(string moduleName)
        {
            var module = GetModuleFromName(moduleName);

            RunProcess(module.FullPathAndName, "setup");
        }

        public static void InstallService(string moduleName, bool start)
        {
            var module = GetModuleFromName(moduleName);

            string arguments = "install" + (start ? " start" : "");

            if (!string.IsNullOrEmpty(module.Username))
            {
                arguments += " -username " + module.Username;

                if (!string.IsNullOrEmpty(module.Password))
                    arguments += " -password " + module.Password;
            }

            RunProcess(module.FullPathAndName, arguments);
        }

        public static void UninstallService(string moduleName)
        {
            var module = GetModuleFromName(moduleName);
            
            RunProcess(module.FullPathAndName, "uninstall");
        }

        private static void RunProcess(string filename, string arguments)
        {
            ProcessStartInfo process = new ProcessStartInfo();

            process.FileName = filename;
            process.Arguments = arguments;
            process.WindowStyle = ProcessWindowStyle.Hidden;
            process.CreateNoWindow = true;

            Process.Start(process);
        }

        public static void StartModule(string moduleName)
        {
            Trace.TraceInformation("Starting module: " + moduleName);
            ServiceController service = GetServiceFromModuleName(moduleName);
            if(service == null) // not installed
                InstallService(moduleName, true);
            else
            {
                if(service.Status == ServiceControllerStatus.Stopped)
                    service.Start();
            }

            CheckForModules();
        }

        public static void StopModule(string moduleName)
        {
            ServiceController service = GetServiceFromModuleName(moduleName);
            if (service != null)
            {
                service.Stop();

                UninstallService(moduleName);
            }

            CheckForModules();
        }
        
        private static ServiceController GetServiceFromModuleName(string moduleName)
        {
            var module = GetModuleFromName(moduleName);
            try
            {
                ServiceController service = new ServiceController(module.ServiceName);
                string status = service.Status.ToString(); // this will crash if the service is not installed
                if(status != null) // just make sure that status check is not optimized out
                    return service;
            }
            catch
            {
            }

            return null;
        }

        private static ModuleInfo GetModuleFromServiceName(string serviceName)
        {
            var moduleInfo = _moduleInfos.FirstOrDefault(m => m.ServiceName.Equals(serviceName));
            return moduleInfo;
        }

        public static ModuleInfo GetModuleFromName(string moduleName)
        {
            var module = _moduleInfos.SingleOrDefault(m => m.Name.Equals(moduleName, StringComparison.InvariantCultureIgnoreCase));
            if (module == null)
                throw new ApplicationException("Module with the name " + moduleName + " does not exist");

            return module;
        }
        
        private static ServiceControllerStatus? GetServiceStatus(string serviceName)
        {
            try
            {
                ServiceController service = new ServiceController(serviceName);
                return service.Status;
            }
            catch (Exception)
            {
                return null;
            }

            /*try
            {
                ServiceController myService = new ServiceController(serviceName);

                switch (myService.Status)
                {
                    case ServiceControllerStatus.Stopped:
                        return ModuleStatus.Stopped;
                    case ServiceControllerStatus.StartPending:
                        return ModuleStatus.Starting;
                    case ServiceControllerStatus.StopPending:
                        return ModuleStatus.Stopping;
                    case ServiceControllerStatus.Running:
                        return ModuleStatus.Running;
                    case ServiceControllerStatus.PausePending:
                        return ModuleStatus.Pausing;
                        
                }
            }
            catch (Exception)
            {
                return ModuleStatus.NotInstalled;
            }*/
        }

        public static void Pulse(string serviceName, string message)
        {
            var module = GetModuleFromServiceName(serviceName);
            module.LastPulseMessage = message;
            module.LastPulseUtc = DateTime.UtcNow;
        }

        public static List<ModuleInfo> GetAllModuleInfos()
        {
            return _moduleInfos.ToList();
        }

        /*public enum ModuleStatus
        {
            NotInstalled = 0,
            Stopping = 100,
            Stopped = 200,
            Starting = 200,
            Running = 400,
            Pausing = 500,
            Paused = 600
        }*/

        public class ModuleInfo
        {
            public string Name { get; set; }
            public string Filename { get; set; }
            public string ServiceName { get; set; }
            public string FullPathAndName { get; set; }
            public ServiceControllerStatus? Status { get; set; }
            public bool Enabled { get; set; }
            public string Username { get; set; }
            public string Password { get; set; }

            public string LastPulseMessage;
            public DateTime? LastPulseUtc { get; set; }
        }
    }
    
}
